package com.nulldev.util.logging;

import java.io.PrintStream;
import java.util.Arrays;
import java.util.logging.Level;

import com.nulldev.util.io.fastIO.FastIO;
import com.nulldev.util.java.JVMUtil;
import com.nulldev.util.string.StringsUtil;

public class Logger {
	
	/* Added in 2.6-beta b210 */
	/**
	 * USE_NEW_LOOK sets nLogger to use the new look when outputting data. <br>
	 * Changes: <br>
	 * INFO: <br>
	 * [nLogger] [INFO@Class] Message ==> [i] [Class] Message or [i] Message
	 */
	public boolean USE_NEW_LOOK = false;
	public boolean NEW_LOOK_DISPLAY_CLASS_NAME = true;

	public boolean infoEnabled = true;
	public boolean warnEnabled = true;
	public boolean errorEnabled = true;
	public boolean debugEnabled = false;
	public boolean traceEnabled = false;

	public PrintStream outputDevice = System.out;
	public PrintStream errorDevice = System.err;

	public static final Logger instance = new Logger("generic.class");

	private String className;
	public Logger(final String className) {
		this.className = className;
	}

	public void info(String string) {
		if (!this.infoEnabled) {
			return;
		}
		if (!this.USE_NEW_LOOK) {
			synchronized (this.outputDevice) {
				synchronized (this.errorDevice) {
					FastIO.get().println(outputDevice, "[nullLogger] [INFO @ " + className + "] " + string);
				}
			}
		}else {
			synchronized (this.outputDevice) {
				synchronized (this.errorDevice) {
					FastIO.get().println(outputDevice, "[i] " +
				(this.NEW_LOOK_DISPLAY_CLASS_NAME ? "[" + this.className + "] " : "") + string);
				}
			}
		}
	}
	
	public void infoSL(String string) {
		if (!this.infoEnabled) {
			return;
		}
		if (!this.USE_NEW_LOOK) {
			synchronized (this.outputDevice) {
				synchronized (this.errorDevice) {
					FastIO.get().print(outputDevice, "[nullLogger] [INFO @ " + className + "] " + string);
				}
			}
		}else {
			synchronized (this.outputDevice) {
				synchronized (this.errorDevice) {
					FastIO.get().print(outputDevice, "[i] " +
				(this.NEW_LOOK_DISPLAY_CLASS_NAME ? "[" + this.className + "] " : "") + string);
				}
			}
		}
	}

	public boolean log(Level fine, String string, Object... objects) {
		if (fine == Level.SEVERE) {
			this.error(string, objects[0]);
		}else if (fine == Level.FINEST) {
			this.trace(string, objects);
		}else if (fine == Level.FINE) {
			this.debug(string, objects);
		}else if (fine == Level.WARNING) {
			this.warn(string, objects);
		}else if (fine == Level.INFO) {
			this.info(string, objects);
		}
		return true;
	}

	public void warn(String string) {
		if (!this.warnEnabled) {
			return;
		}
		if (!this.USE_NEW_LOOK) {
			FastIO.get().println(outputDevice, "[nullLogger] [Warning @ " + className + "] " + string);
		}else {
			synchronized (this.outputDevice) {
				synchronized (this.errorDevice) {
					FastIO.get().println(outputDevice, "[!] " +
							(this.NEW_LOOK_DISPLAY_CLASS_NAME ? "[" + this.className + "] " : "") + string);
				}
			}
		}
	}
	
	public void warn(String string, final Object object) {
		if (!this.warnEnabled) {
			return;
		}
		if (!this.USE_NEW_LOOK) {
			FastIO.get().println(outputDevice, "[nullLogger] [Warning @ " + className + "] " + string + " [obj=" + object + "]");
		}else {
			synchronized (this.outputDevice) {
				synchronized (this.errorDevice) {
					FastIO.get().println(outputDevice, "[!] " +
							(this.NEW_LOOK_DISPLAY_CLASS_NAME ? "[" + this.className + "] " : "") + string + " | " + object);
				}
			}
		}
	}

	public void error(String string) {
		if (!this.errorEnabled) {
			return;
		}
		if (!this.USE_NEW_LOOK) {
			synchronized (outputDevice) {
				synchronized (errorDevice) {
					FastIO.get().println(errorDevice, "[nullLogger] [Error @ " + className + "] " + string);
				}
			}
		}else {
			synchronized (this.outputDevice) {
				synchronized (this.errorDevice) {
					FastIO.get().println(errorDevice, "[!!] " +
							(this.NEW_LOOK_DISPLAY_CLASS_NAME ? "[" + this.className + "] " : "") + string);
				}
			}
		}
	}

	public void error(String string, Object value) {
		if (!this.errorEnabled) {
			return;
		}
		if (!this.USE_NEW_LOOK) {
			synchronized (this.outputDevice) {
				synchronized (this.errorDevice) {
					FastIO.get().println(errorDevice, "[nullLogger] [Error @ " + className + "] " + string + " [" + value + "]");
				}
			}
		}else {
			synchronized (this.outputDevice) {
				synchronized (this.errorDevice) {
					FastIO.get().println(errorDevice, "[!!] " +
							(this.NEW_LOOK_DISPLAY_CLASS_NAME ? "[" + this.className + "] " : "") + string + " | " + value);
				}
			}
		}
	}
	
	public void error(String string, Throwable value) {
		if (!this.errorEnabled) {
			return;
		}
		if (!this.USE_NEW_LOOK) {
			synchronized (outputDevice) {
				synchronized (errorDevice) {
					FastIO.get().println(errorDevice, "[nullLogger] [Error @ " + className + "] " + string);
					FastIO.get().println(errorDevice, JVMUtil.fromError(value));
				}
			}
		}else {
			synchronized (this.outputDevice) {
				synchronized (this.errorDevice) {
					FastIO.get().println(errorDevice, "[!!] " +
							(this.NEW_LOOK_DISPLAY_CLASS_NAME ? "[" + this.className + "] " : "") + string);
					FastIO.get().println(errorDevice, JVMUtil.fromError(value));
				}
			}
		}
	}

	public void debug(String string) {
		if (!this.debugEnabled) {
			return;
		}
		if (!this.USE_NEW_LOOK) {
			synchronized (errorDevice) {
				synchronized (outputDevice) {
					FastIO.get().println(outputDevice, "[nullLogger] [Debug @ " + className + "] " + string);
				}
			}
		}else {
			synchronized (this.errorDevice) {
				synchronized (this.outputDevice) {
					FastIO.get().println(outputDevice, "[D] " +
							(this.NEW_LOOK_DISPLAY_CLASS_NAME ? "[" + this.className + "] " : "") + string);
				}
			}
		}
	}
	
	public void debugC(final String string) {
		if (!this.debugEnabled) {
			return;
		}
		if (!this.USE_NEW_LOOK) {
			FastIO.get().println(outputDevice, "[nullLogger] " + string);
		}else {
			FastIO.get().println(outputDevice, "[D] " + string);
		}
	}

	public void debug(String string, Object... objects) {
		if (!this.debugEnabled) {
			return;
		}
		if (!this.USE_NEW_LOOK) {
			FastIO.get().println(outputDevice, "[nullLogger] [Debug @ " + className + "] " + string + " [Value(s): " + Arrays.toString(objects) + "]");
		}else {
			synchronized (this.errorDevice) {
				synchronized (this.outputDevice) {
					FastIO.get().println(outputDevice, "[D] " +
					(this.NEW_LOOK_DISPLAY_CLASS_NAME ? "[" + this.className + "] " : "") + string + " | " + Arrays.toString(objects));
				}
			}
		}
		//outputDevice.println("[nullLogger] [Debug @ " + className + "] " + StringsUtil.format(String.format(string, objects), objects));
	}

	public void trace(String string) {
		if (!this.traceEnabled) {
			return;
		}
		if (!this.USE_NEW_LOOK) {
			outputDevice.println("[nullLogger] [Trace @ " + className + "] " + string);
		}else {
			synchronized (this.errorDevice) {
				synchronized (this.outputDevice) {
					FastIO.get().println(outputDevice, "[T] " +
							(this.NEW_LOOK_DISPLAY_CLASS_NAME ? "[" + this.className + "] " : "") + string);
				}
			}
		}
	}

	public boolean isErrorEnabled() {
		return this.errorEnabled;
	}

	public void error(Exception e) {
		if (!this.errorEnabled) {
			return;
		}
		if (!this.USE_NEW_LOOK) {
			synchronized (this.errorDevice) {
				synchronized (this.outputDevice) {
					FastIO.get().println(errorDevice, "[nullLogger] [Error @ " + className + "] " + e.getMessage() + " [" + e.getClass().getName() + "]");
				}		
			}
		}else {
			synchronized (this.errorDevice) {
				synchronized (this.outputDevice) {
					FastIO.get().println(errorDevice, "[D] " +
							(this.NEW_LOOK_DISPLAY_CLASS_NAME ? "[" + this.className + "] " : ""));
					FastIO.get().println(errorDevice, JVMUtil.fromError(e));
				}
			}
		}
	}

	public void trace(final String string, final String val) {
		if (!this.traceEnabled) {
			return;
		}
		if (!this.USE_NEW_LOOK) {
			FastIO.get().println(outputDevice, "[nullLogger] [Trace @ " + className + "] " + string + " [Value: " + val + "]");
		}else {
			synchronized (this.errorDevice) {
				synchronized (this.outputDevice) {
					FastIO.get().println(outputDevice, "[T] " +
							(this.NEW_LOOK_DISPLAY_CLASS_NAME ? "[" + this.className + "] " : "") + string + " | " + val);
				}
				
			}
		}
	}
	
	public void trace(final String string, final Object val) {
		if (!this.traceEnabled) {
			return;
		}
		if (!this.USE_NEW_LOOK) {
			FastIO.get().println(outputDevice, "[nullLogger] [Trace @ " + className + "] " + string + " [Value: " + val + "]");
		}else {
			synchronized (this.errorDevice) {
				synchronized (this.outputDevice) {
					FastIO.get().println(outputDevice, "[T] " +
							(this.NEW_LOOK_DISPLAY_CLASS_NAME ? "[" + this.className + "] " : "") + string + " | " + val);
				}
			}
		}
	}
	
	public void trace(final String string, final Object val, final Object val1) {
		if (!this.traceEnabled) {
			return;
		}
		if (!this.USE_NEW_LOOK) {
			FastIO.get().println(outputDevice, "[nullLogger] [Trace @ " + className + "] " + string + " [Value: " + val + ", " + val1 + "]");
		}else {
			synchronized (this.errorDevice) {
				synchronized (this.outputDevice) {
					FastIO.get().println(outputDevice, "[T] " +
							(this.NEW_LOOK_DISPLAY_CLASS_NAME ? "[" + this.className + "] " : "") + string + " | " + val + ", " + val1);
				}
			}
		}
	}

	public void trace(final String string, final String val, final Exception ex) {
		if (!this.traceEnabled) {
			return;
		}
		if (!this.USE_NEW_LOOK) {
			FastIO.get().println(outputDevice, "[nullLogger] [Trace @ " + className + "] " + string + " [Value: " + val + "]");
			FastIO.get().println(outputDevice, "[nullLogger] Printing exception...");
			FastIO.get().println(outputDevice, JVMUtil.fromError(ex));
		}else {
			synchronized (this.errorDevice) {
				synchronized (this.outputDevice) {
					FastIO.get().println(outputDevice, "[T] " +
							(this.NEW_LOOK_DISPLAY_CLASS_NAME ? "[" + this.className + "] " : "") + string + " | " + val);
					FastIO.get().println(outputDevice, JVMUtil.fromError(ex));
				}
			}
		}
	}

	public void info(final String string, final String val) {
		if (!this.infoEnabled) {
			return;
		}
		if (!this.USE_NEW_LOOK) {
			FastIO.get().println(outputDevice, "[nullLogger] [INFO @ " + className + "] " + string + " [Value: " + val + "]");
		}else {
			synchronized (this.errorDevice) {
				synchronized (this.outputDevice) {
					FastIO.get().println(outputDevice, "[i] " +
							(this.NEW_LOOK_DISPLAY_CLASS_NAME ? "[" + this.className + "] " : "") + string + " | " + val);
				}
			}
		}
	}
	
	public void info(final String string, final Object... objects) {
		if (!this.infoEnabled) {
			return;
		}
		if (!this.USE_NEW_LOOK) {
			//outputDevice.println("[nullLogger] [INFO @ " + className + "] " + string + " [Value: " + Arrays.toString(objects) + "]");
			FastIO.get().println(outputDevice, "[nullLogger] [INFO @ " + className + "] " + String.format(string, objects));
		}else {
			synchronized (this.errorDevice) {
				synchronized (this.outputDevice) {
					FastIO.get().println(outputDevice, "[i] " +
							(this.NEW_LOOK_DISPLAY_CLASS_NAME ? "[" + this.className + "] " : "") + string + " | " + Arrays.toString(objects));
				}
			}
		}
	}

	public void error(final String string, final String val, final Throwable ex) {
		if (!this.errorEnabled) {
			return;
		}
		if (!this.USE_NEW_LOOK) {
			FastIO.get().println(outputDevice, "[nullLogger] [Error @ " + className + "] " + string + " [Value: " + val + "]");
			FastIO.get().println(errorDevice, "[nullLogger] Printing exception...");
			FastIO.get().println(errorDevice, JVMUtil.fromError(ex));
		}else {
			synchronized (this.errorDevice) {
				synchronized (this.outputDevice) {
					FastIO.get().println(outputDevice, "[!!] " +
							(this.NEW_LOOK_DISPLAY_CLASS_NAME ? "[" + this.className + "] " : "") + string + " | " + val);
					FastIO.get().println(errorDevice, JVMUtil.fromError(ex));
				}
				
			}
		}
	}

	/**
	 * Bypasses isInfoEnabled param.
	 * @param string
	 */
	public void binfo(final String string) {
		synchronized (this.errorDevice) {
			synchronized (this.outputDevice) {
				if (!this.USE_NEW_LOOK) {
					FastIO.get().println(outputDevice, "[nullLogger] [INFO @ " + className + "] " + string);
				}else {
					FastIO.get().println(outputDevice, "[i] " +
							(this.NEW_LOOK_DISPLAY_CLASS_NAME ? "[" + this.className + "] " : "") + string);
				}
			}
		}
	}
	
	public void binfoEA(final String string) {
		synchronized (this.errorDevice) {
			synchronized (this.outputDevice) {
				if (!this.USE_NEW_LOOK) {
					FastIO.get().print(outputDevice, "[nullLogger] [INFO @ " + className + "] " + string);
				}else {
					FastIO.get().print(outputDevice, "[i] " +
							(this.NEW_LOOK_DISPLAY_CLASS_NAME ? "[" + this.className + "] " : "") + string);
				}
			}
		}
	}

	public void binfoE(final String string) {
		synchronized (this.errorDevice) {
			synchronized (this.outputDevice) {
				FastIO.get().print(outputDevice, string);
			}
		}
	}
	
	public void binfoEN(final String string) {
		synchronized (this.errorDevice) {
			synchronized (this.outputDevice) {
				FastIO.get().println(outputDevice, string);
			}
		}
	}

	public void finest(String string) {
		this.trace(string);
	}

	public boolean isLoggingEnabled() {
		return this.infoEnabled;
	}

	public void warning(final String string) {
		this.warn(string);
	}

	public void debugE(String string) {
		if (this.debugEnabled) {
			synchronized (this.errorDevice) {
				synchronized (this.outputDevice) {
					outputDevice.print(string);
				}
			}
		}
	}

	public void overrideName(final String string) {
		this.className = string;
	}

	public void verb(final String string) {
		this.trace(string);
	}
	
	public static enum Level {
		SEVERE, FINEST, FINE, WARNING, INFO
		
	}

	public static Logger getLogger(final String name) {
		return new Logger(name);
	}
}
